package redis.jedis ;

import java.util.HashMap ;
import java.util.Map ;

import org.springframework.util.StringUtils ;

import redis.clients.jedis.Jedis ;
import redis.clients.jedis.JedisPool ;
import redis.clients.jedis.JedisPoolConfig ;

/**
 * 获取Jedis对象的工具类,设计为单利模式
 * @author 80002165 @date 2017年6月22日 上午8:49:21
 */
public class JedisUtil {
    
    // jedis连接池Map
    private static final Map<String, JedisPool> poolMaps = new HashMap<String, JedisPool>() ;
    
    private JedisUtil() {
    }
    
    /**
     * 类级的内部类，也就是静态的成员式内部类，该内部类的实例与外部类的实例 没有绑定关系，而且只有被调用到时才会装载，从而实现了延迟加载。
     */
    public static class RedisUtilHolder {
        /**
         * 静态初始化器，由JVM来保证线程安全
         */
        private static JedisUtil instance = new JedisUtil() ;
    }
    
    /**
     * 当getInstance方法第一次被调用的时候，它第一次读取
     * RedisUtilHolder.instance，导致RedisUtilHolder类得到初始化；而这个类在装载并被初始化的时候，会初始化它的静
     * 态域，从而创建RedisUtil的实例，由于是静态的域，因此只会在虚拟机装载类的时候初始化一次，并由虚拟机来保证它的线程安全性。
     * 这个模式的优势在于，getInstance方法并没有被同步，并且只是执行一个域的访问，因此延迟初始化并没有增加任何访问成本。
     */
    public static JedisUtil getInstance() {
        return RedisUtilHolder.instance ;
    }
    
    /**
     * 获取jedis连接池
     * @author 80002165 @date 2017年6月22日 上午9:04:26
     * @param ip
     * @param port
     * @param password
     * @return
     */
    private static JedisPool getPool(String ip, int port, String password) {
        String key = String.format("%s:%d", ip, port) ;
        JedisPool pool = null ;
        
        // 这里为了提供大多数情况下线程池Map里面已经有对应ip的线程池直接返回，提高效率
        if (poolMaps.containsKey(key)) {
            pool = poolMaps.get(key) ;
            return pool ;
        }
        
        // 这里的同步代码块防止多个线程同时产生多个相同的ip线程池
        synchronized (JedisUtil.class) {
            if (!poolMaps.containsKey(key)) {
                JedisPoolConfig config = new JedisPoolConfig() ;
                config.setMaxTotal(RedisConfig.getMaxTotal()) ;
                config.setMaxIdle(RedisConfig.getMaxIdle()) ;
                config.setMaxWaitMillis(RedisConfig.getMaxWaitMillis()) ;
                config.setTestOnBorrow(true) ;
                config.setTestOnReturn(true) ;
                
                /**
                 * 如果你遇到 java.net.SocketTimeoutException: Read timed out
                 * exception的异常信息 请尝试在构造JedisPool的时候设置自己的超时值.
                 * JedisPool默认的超时时间是2秒(单位毫秒)
                 */
                try {
                    if (StringUtils.hasText(password)) {
                        pool = new JedisPool(config, ip, port, RedisConfig.getTimeout(), password) ;
                    } else {
                        pool = new JedisPool(config, ip, port, RedisConfig.getTimeout()) ;
                    }
                } catch (Exception e) {
                    System.out.println("创建jedis连接池出错！") ;
                    e.printStackTrace() ;
                }
                poolMaps.put(key, pool) ;
            } else {
                pool = poolMaps.get(key) ;
            }
        }
        return pool ;
    }
    
    /**
     * 获取jedis对象
     * @author 80002165 @date 2017年6月22日 上午9:04:42
     * @param ip
     * @param port
     * @param password
     * @return
     */
    @SuppressWarnings("deprecation")
    public Jedis getJedis(String ip, int port, String password) {
        Jedis jedis = null ;
        // 记录操作数，用来判断是否大于重试次数
        int count = 0 ;
        do {
            try {
                jedis = getPool(ip, port, password).getResource() ;
            } catch (Exception e) {
                System.out.println("get redis master failed!") ;
                e.printStackTrace() ;
                // 销毁对象
                getPool(ip, port, password).returnBrokenResource(jedis) ;
            }
            
            count++ ;
        } while (jedis == null && count < RedisConfig.getRetryNum()) ;
        return jedis ;
    }
    
    @SuppressWarnings("deprecation")
    /**
     * 释放redis实例到连接池.
     * @author 80002165 @date 2017年6月22日 上午9:05:50
     * @param jedis
     * @param ip
     * @param port
     * @param password
     */
    public void returnJedis(Jedis jedis, String ip, int port, String password) {
        if (jedis != null) {
            getPool(ip, port, password).returnResource(jedis) ;
        }
    }
    
    public static void main(String[] args) {
        JedisUtil jUtil = new JedisUtil() ;
        jUtil.getJedis("127.0.0.1", 6379, null) ;
    }
    
}
